package com.hillfly.mybatis.shard.converter;

import com.hillfly.mybatis.shard.ShardHelper;
import com.hillfly.mybatis.shard.ShardParams;
import com.hillfly.mybatis.shard.annotation.ShardTable;
import com.hillfly.mybatis.shard.annotation.ShardTableIgnore;
import com.hillfly.mybatis.shard.strategy.impl.DoNotStrategy;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SQL转换器抽象类
 */
public abstract class AbstractSqlConverter implements SqlConverter {
    private Logger logger = LoggerFactory.getLogger(getClass());

    // 分表策略缓存
    private static final ConcurrentHashMap<String, ShardParams> cache = new ConcurrentHashMap<String, ShardParams>();

    // 当前分表策略
    private ShardParams shardParams;

    /**
     * 做真正的转换
     *
     * @param statement
     * @param params
     * @return
     */
    public abstract Statement doConvert(final Statement statement, final Object params);

    /**
     * 是否需要转换的Table
     *
     * @param tableName
     * @param shardParams
     * @return
     */
    private boolean isNeedConvertTable(String tableName, ShardParams shardParams) {
        if (StringUtils.isBlank(tableName)) return false;
        if (ArrayUtils.contains(shardParams.getExcludes(), tableName)) return false;
        if (ArrayUtils.isNotEmpty(shardParams.getIncludes()) && !ArrayUtils.contains(shardParams.getIncludes(), tableName))
            return false;
        return true;
    }

    private boolean hasThreadLocalParams() {
        ShardParams params = ShardHelper.getLocalShardParams();
        if (null == params) return false;
        if (params.getStrategy() instanceof DoNotStrategy) return false;
        shardParams = params;
        return true;
    }

    /**
     * 是否存在分表标志
     *
     * @param mapperId
     * @return
     */
    private boolean existShardSign(String mapperId) {
        return hasThreadLocalParams() || hasAnnotation(mapperId);
    }

    /**
     * 是否包含定义的注解
     *
     * @param mapperId Mapper id
     * @return
     */
    private boolean hasAnnotation(String mapperId) {
        if (StringUtils.isBlank(mapperId)) {
            return false;
        }
        logger.debug("[mapperId] " + mapperId);
        // 类名
        String className = StringUtils.substring(mapperId, 0, mapperId.lastIndexOf("."));
        // 方法名
        String methodName = StringUtils.substring(mapperId, mapperId.lastIndexOf(".") + 1);
        logger.debug("[className -> methodName] " + className + " -> " + methodName);

        Method method = null;
        ShardTable typeShardAnno = null;
        ShardTable methodShardAnno = null;
        boolean isIgnorePresent = false;

        try {
            Class<?> clazz = Class.forName(className);
            for (Method m : clazz.getDeclaredMethods()) {
                if (m.getName().equals(methodName)) {
                    method = m;
                }
            }

            isIgnorePresent = method == null ? false : method.isAnnotationPresent(ShardTableIgnore.class);
            if (!isIgnorePresent && method != null && method.isAnnotationPresent(ShardTable.class)) { //存在方法注解
                methodShardAnno = method.getAnnotation(ShardTable.class);
                shardParams = cache.get(mapperId);
                if (null == shardParams) {
                    shardParams = new ShardParams(methodShardAnno.strategy().newInstance());
                    shardParams.setProperties(methodShardAnno);
                    if (methodShardAnno.isInherit() && clazz.isAnnotationPresent(ShardTable.class)) { //需要继承类注解属性
                        typeShardAnno = clazz.getAnnotation(ShardTable.class);
                        if (typeShardAnno.strategy() == methodShardAnno.strategy()) { //策略相同情况进行继承，合并属性
                            shardParams.setExcludes(ArrayUtils.addAll(shardParams.getExcludes(), typeShardAnno.excludes()));
                            shardParams.setIncludes(ArrayUtils.addAll(shardParams.getIncludes(), typeShardAnno.includes()));
                        }
                    }
                    cache.put(mapperId, shardParams);
                }
                return true;
            } else if (clazz.isAnnotationPresent(ShardTable.class) && !isIgnorePresent) {
                typeShardAnno = clazz.getAnnotation(ShardTable.class);
                shardParams = cache.get(className);
                if (null == shardParams) {
                    shardParams = new ShardParams(typeShardAnno.strategy().newInstance());
                    shardParams.setProperties(typeShardAnno);
                    cache.put(className, shardParams);
                }
                return true;
            }
        } catch (ClassNotFoundException e) {
            logger.error("[hasAnnotation ClassNotFoundException]", e);
        } catch (InstantiationException e) {
            logger.error("[hasAnnotation InstantiationException]", e);
        } catch (IllegalAccessException e) {
            logger.error("[hasAnnotation IllegalAccessException]", e);
        } catch (SecurityException e) {
            logger.error("[hasAnnotation SecurityException]", e);
        }
        return false;
    }

    /**
     * Statement对象反解析成sql语句
     *
     * @param statement
     * @return
     */
    private String doDeParse(Statement statement) {
        if (statement == null) return null;
        StatementDeParser deParser = new StatementDeParser(new StringBuilder());
        statement.accept(deParser);
        return deParser.getBuffer().toString();
    }

    /**
     * 获取分表后的TableName
     *
     * @param baseTableName 基础表名
     * @param params        传入参数
     * @return
     */
    protected String getFinalTable(String baseTableName, Object params) {
        Assert.hasText(baseTableName, "baseTableName must have length; it must not be null or empty");
        if (shardParams != null) {
            if (isNeedConvertTable(baseTableName, shardParams)) {
                logger.debug("[currentStrategy ClassName] " + shardParams.getStrategy().getClass().getName());
                return shardParams.getStrategy().getFinalTableName(baseTableName, params);
            }
        }
        return baseTableName;
    }

    @Override
    public String convert(Statement statement, Object params, String mapperId) {
        if (statement == null || !existShardSign(mapperId)) {
            return null;
        }
        return doDeParse(doConvert(statement, params));
    }
}
